<?php

/*
 *  This collection of functions (libary) is licensed under GPL2.
 *  See LICENSE or www.gnu.org for more details.
 *
 *  Author: Henri Ranki & Jyry Kuukkanen 3.7.2001,
 *
 *  $Id: dzlaituri.php 595 2006-04-03 16:00:47Z jyry $
 */

include_once("sodebug.php");
include_once("sostring.php");
include_once("sogeneric.php");

include_once("dzitu.php");
include_once("dzconst.php");
include_once("dzgeneric.php");
include_once("dztemplate.php");
include_once("dzpurkki.php");
include_once("dzrpc.php");
include_once("dzerror.php");


/** Class for managing data connection in Dataxi
 *  Takes care of browsing, reading and writing of data to and from relational
 *      tables in a database.
 *
 *  @var string $__StructName The struct name this is the source file name
 *      without path and extension.
 *  @var string Alias for db information in Kone. Kone search the db connect
 *      information according to this alias. DB acces information is stored in
 *      kone.ini file
 *  @var soArraySet/any $__BrowseKeys Browse key list.
 *  @var int $__BrowsePos int, current position in $BrowseKeys
 *  @var int $__BrowseKeyCount Number of keys in the browse key list
 *  @var dzSessionInfo $__SessionInfo Basic session information
 *  @var string $__DataSetLockedBy User who has locked current data set
 *  @var int $__LockingInfo The lock status.
 *  @var object dzStruct Structure
 */
class dzLaituri extends dzPurkki {
    var $__StructName;
    var $__DbAlias;
    var $__BrowseKeys;
    var $__BrowsePos;
    var $__BrowseKeyCount;
    var $__SessionInfo;
    var $__DataSetLockedBy;
    var $__LockingInfo;
    var $__Struct;


    /** constructor
     *  @param string StructName Name of the structure that defines the form
     *  @param dzSessionInfo $BasicSessionInfo Session owner and id information
     *  @param string $DbAlias Alias that point to one dbaccess string in
     *  kone.ini
     */
    function dzLaituri($StructName, $BasicSessionInfo, $DbAlias) {
        /* Purkki's constructor must be called explicitely. */
        $this->dzPurkki();

        $this->__SessionInfo = $BasicSessionInfo;
        $this->__BrowseKeys = new soArraySet();

        $this->__StructName = $StructName;
        $paths = dzGetSourceCachePaths($StructName);

        $this->__Struct = unserialize(soFileToString($paths["struct"]));
        $this->__DbAlias = $DbAlias;

        $kone_access = dzGetIniItem(DZIS_GENERAL,
                                    "kone_access_cache", "app", "bool");
        if ($kone_access) {
            soDebug("dzLaituri: Kone has access to struct cache", SOD_DET);
        } else {
            soDebug("dzLaituri: Kone has NO access to struct cache", SOD_DET);
        } // if soCaseConv
    } // constructor


    /** Return the the lock information for the current data set
     */
    function getLockedBy() {
        return $this->__DataSetLockedBy;
    } // getLockedBy


    /** Return the the lock information for the current data set
     */
    function getLockInfo() {
        return $this->__LockingInfo;
    } //getLockInfo


    /** function getBrowsePos()
     *  Returns current position in BrowseKeys
     */
    function getBrowsePosStr() {
        return ($this->__BrowsePos + 1)."/".$this->__BrowseKeyCount;
    } // getBrowsePosStr


    /** Get the amount of found browse keys
     *  >:int Amount of the found browse keys
     */
    function getBrowseKeyCount() {
        return $this->__BrowseKeyCount;
    } // getBrowseKeyCount

    /** function getBrowsePos()
     *  Returns current position in BrowseKeys
     */
    function getBrowsePos() {
        return $this->__BrowsePos;
    } // getBrowsePos


    /** Move browse pointer
     *  Moves pointer forward or backward.
     *  @param int $Steps Amount of steps to move. Negative numbers move the 
     *      pointer backward.
     *  @return int Result position or current on error.
     */
    function jumpBrowsePos($Steps) {
        return $this->moveBrowsePos($this->__BrowsePos + $Steps);
    } // jumpBrowsePos


    /** Moves browse pointer to new absolute position.
     *  @param int $NewPos The position to move to.
     *  @return int Result position or current on error.
     */
    function moveBrowsePos($NewPos) {
        /* Check that position is valid */
        if (($NewPos >= 0) && ($NewPos < $this->__BrowseKeyCount))
            $this->__BrowsePos = $NewPos;

        return $this->__BrowsePos;
    } // moveBrowsePos


    /** Get the browse key columns
     *  This is really confusing name: should be getBrowseKeyCols --jq
     *  @return array/any The list of browse keys
     */
    function getBrowseKeys() {
        if (soGetClass($this->__BrowseKeys) == "soarrayset") {
            $result = $this->__BrowseKeys->getKeys();
        } else {
            $result = array();
        };
        return $result;
    } // getBrowseKeys


    /** Gets the browse list values
     *  @return array/any The list of browse keys XXXX
     */
    function getBrowseList() {
        return $this->__BrowseKeys;
    } // getBrowseKeys


    /** Set values for one browse key column
     *  @param string $Col Column which values to set
     *  @param array/string $Values Values to set
     */
    function setBrowseKeyCol($Col, $Values) {
        $this->__BrowseKeys->setItem($Col, $Values);
    } // setBrowseKeyCol


    /** Get value(s) for current browse key.
     *  Returns the main key for a set currently under focus by
     *  $this->BrowsePos
     *  @return soArraySet/any The key to current set.
     */
    function getCurrentBrowseKey() {
        $result = new soArraySet();
        if ($this->__BrowseKeyCount > 0) {
            /* get column names in browselist */
            $keys = $this->__BrowseKeys->getKeys();

            /* loop all columns for each row */
            foreach ($keys as $key) {
                $col = &$this->__BrowseKeys->getItem($key);
                $result->setArrayItem($key, -1, $col[$this->__BrowsePos]);
            }; // foreach
        } // if $this->__BrowseKeyCount

        return $result;
    } // getCurrentBrowseKey


    /** Creates Itu and fills th basic information
     * New itu is created and the basic control data is set.
     * @return dzItu created Itu
     */
    function createItu() {
        $result = new dzItu();

        /* Set the session id for locks and user information to access
         * database */
        $result->setBlockItem(DZI_CONTROL, DZK_SESSION,
                              $this->__SessionInfo->Id);
        $result->setBlockItem(DZI_CONTROL, DZK_USER,
                              $this->__SessionInfo->UserID);
        $result->setBlockItem(DZI_CONTROL, DZK_OWNER,
                              $this->__SessionInfo->UserID);
        $result->setBlockItem(DZI_CONTROL, DZK_DATABASE,
                              $this->__DbAlias);

        /* Set the db access information */
        $kone_access = dzGetIniItem(DZIS_GENERAL,
                                    "kone_access_cache", "app", "bool");
        if ($kone_access) {
            $result->setBlockItem(DZI_CONTROL,
                                  DZK_STRUCT,
                                  "file://".$this->__Struct->getName());
        } else {
            $result->setBlockItem(DZI_CONTROL,
                                  DZK_STRUCT,
                                  "object://".serialize($this->__Struct));
        } // else if

        return $result;
    } // createItu


    /** Read in the data in current browse position
     *  Send a request to Kone. Requests data for the current browse position.
     *  Takes the key from the browse list and sends it to Kone, which returns
     *  the rest of the data if all ok.
     *
     *  If locking is required the rows are locked first if possible. If not
     *  possible an error code is returned in the Itu.
     *
     *  @param bool Lock If true rows are locked before the reading
     *  @return dzErrorMsg   Error message or NULL if all ok.
     */
    function readSet($Lock = false) {
        /* check that there is row to get */
        if (($this->__BrowseCount) &&
            ($this->__BrowsePos < $this->__BrowseCount) &&
            ($this->__BrowsePos >= 0)) return 0; // <-- EXIT!!

        /* Construct the request to send to Kone. First create Itu oand fill
           the basic control information in */
        $request = $this->createItu();

        /* Set the request type */
        if ($Lock) {
            $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_LOCK);
        } else {
            $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_READ);
        };

        /* Insert the key of the requested data as payload */
        $key_data = $this->getCurrentBrowseKey();
        $request->setBlock(DZI_DATA, $key_data);

        /* Finally send the request */
        $result_itu = sendRpcToKone($request);

        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text",
                                  getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR,
                                     DZREC_NO_ACTION, $error_param);

            return $result;     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code */
        $kone_result = $result_itu->getBlockItemAt(DZI_CONTROL,
                                                   DZK_RESULT, 0);

        /* if result ok get the payload and store it */
        if ($kone_result > 0) {
            sodebug("dzLaituri: read request ok", SOD_DET);
            $this->emptyStore(TISKI);
            $this->joinItu($result_itu->getBlock(DZI_DATA));
            $this->__DataSetLockedBy =
                $result_itu->getBlockItemAt(DZI_CONTROL, DZK_STATUS_TEXT);
            $this->__LockingInfo =
                $result_itu->getBlockItemAt(DZI_CONTROL, DZK_STATUS);
            /* copy the data to TAFFEL as well */
            $this->copy(TISKI, TAFFEL);
            $result = NULL;
        } else {
            sodebug("dzLaituri: read request failed", SOD_DET);
            $result = new dzErrorMsg(DZER_READ_ERROR, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR);
        } // if result

        return $result;
    } // readSet


    /** Writes the current TISKI to db.
     *  @param string $Lang Language code, used to produce error messages.
     *  @param object dzTemplate $Template Template object
     *  @return dzErrorMsg Error message or NULL if all ok.
     */
    function writeSet($Lang, &$Template) {
        /* Construct the request to send to Kone. First create Itu and fill
         * the basic control information in */
        $request = $this->createItu();
        $presave = $Template->getFormEvent(DZE_PRE_SAVE, DZ_SK_EVAL);

        /* Process pre-save options */
        $errors = $this->__preSave(TISKI, $Lang, $presave);
        if ($errors) return $errors; // <-- EXIT

        /* Hash=md5? -- hash column that require hashing */
        $cols = $this->getColnames(TISKI);
        foreach ($cols as $col) {
            $hash = $Template->getColProperty($col, DZCPT_CONTROL, DZCP_HASH);
            if ($hash == "md5") {
                $values = $this->getStoreCol(TULLI, $col);
                $old_vals = $this->getStoreCol(TAFFEL, $col);
                $icount = count($values);
                for ($i = 0; $i < $icount; $i++) {
                    /* No hashing when the value has not been changed */
                    if ($old_vals[$i] != $values[$i]) {
                        $values[$i] = md5($values[$i]);
                    }; // if
                }; // for
                $this->setStoreCol(TISKI, $col, $values);
            }; // if hash
        }; // foreach

        /* Validate the set  */
        $errors = $this->__validateSet(TISKI, $Lang);
        if ($errors) return $errors; // <-- EXIT

        /* Set the request type and insert data block */
        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_WRITE);
        $request->setBlock(DZI_DATA, $this->getStore(TISKI));

        /* Finally send the request */
        $result_itu = sendRpcToKone($request);

        /* check for rpc error */
        if (isRpcError($result_itu)) {
           sodebug("dzLaituri: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text",
                                  getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR,
                                     DZREC_NO_ACTION, $error_param);
            return array($result);     // <-- EXIT !!!
        }; // if (isRpcError($result_itu))

        /* Get the result code */
        $result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);
        /* Check the result */
        if ($result == DZR_OK) {
            soDebug("dzLaituri: WriteSet ok", SOD_DET);
            /* Save the new key to TISKI */
            $key_vals = $result_itu->getBlockItem(DZI_CONTROL, DZK_KEYS);
            $browse_keys = $this->getBrowseKeys();
            $cnt = 0;
            foreach ($browse_keys as $key) {
                $this->setStoreItem(TISKI, $key, 0, $key_vals[$cnt++]);
            } // foreach
            $result = NULL;
        } else {
            soDebug("dzLaituri: WriteSet failed", SOD_LO_E);
            $sql = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_SQL, 0);
            soDebug("dzLaituri: Used sql clause was: $sql", SOD_LO_E);
            $result = array(new dzErrorMsg(DZER_WRITE_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR,
                                     DZREC_NO_ACTION));
        } // if $result

        return $result;
    } // writeSet


    /** Deletes the set at the current browse location from the database
     *  @return dzErrorMsg Error message or NULL if all ok.
     */
    function deleteSet() {
        /* check that there is row to get */
        if (($this->__BrowseCount) &&
            ($this->__BrowsePos < $this->__BrowseCount) &&
            ($this->__BrowsePos >= 0)) return false; // <-- EXIT!!

        /* Construct the request to send to Kone. First create Itu oand fill
           the basic control information in */
        $request = $this->createItu();

        /* Set the request type */
        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_DELETE);

        $payload = new soArraySet();
        $keys = $this->__BrowseKeys->getAsArray();
        foreach ($keys as $key => $data) {
            $payload->setArrayItem($key, -1, $data[$this->__BrowsePos]);
        } // foreach
        $request->setBlock(DZI_DATA, $payload);

        /* The whole TISKI is sent to kone now --> not eficient */
        $result_itu = sendRpcToKone($request);

        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text",
                                  getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR,
                                     DZREC_NO_ACTION, $error_param);

            return array($result);     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code */
        $result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);

        /* if result ok get the payload and store it */
        if ($result > 0) {
            soDebug("dzLaituri: Set deletion ok.", SOD_DET);
            /* Remove the current browse key*/
            $keys = $this->__BrowseKeys->getAsArray();
            foreach ($keys as $key => $data) {
                soDelElement($data, $this->__BrowsePos);
                $this->__BrowseKeys->setItem($key, $data, 1);
            } // foreach
            $this->__BrowseKeyCount--;
            if ($this->__BrowsePos >= $this->__BrowseKeyCount)
                $this->__BrowsePos = $this->__BrowseKeyCount - 1;
            $result = NULL;
        } else {
            soDebug("dzLaituri: Set deletion failed.", SOD_LO_E);
            $result = array(new dzErrorMsg(DZER_DELETE_FAILED, "Laituri",
                                           DZERT_SYSTEM, DZERL_ERROR));
        } // if result

    } // deleteSet


    /** Builds and runs a query based on data in TISKI.
     *  Queries db based on given conditions in TISKI.
     * @return dzErrorMsg   Error message or NULL if all ok.
     */
    function runQuery() {
        /* Construct the request to send to Kone. First create Itu oand fill
           the basic control information in */
        $request = $this->createItu();

        /* Set the request type */
        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_QUERY);

        /* Insert data from the TISKI as payload. The data on form
           is the filter condition for the query  */
        $request->setBlock(DZI_DATA, $this->getStore(TISKI));

        /* Finally send the request */
        $result_itu = sendRpcToKone($request);
        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text", getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                    DZERT_SYSTEM, DZERL_ERROR,
                                    DZREC_NO_ACTION, $error_param);

            return $result;     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code -- result[0] holds the number of sets found,
         * less than 0 means an error in query */
        $result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);

        /* Check the result. If ok store the browse keys */
        if ($result >= 0) {
            $this->__BrowseKeyCount = $result;
            $this->__BrowseKeys = $result_itu->getBlock(DZI_DATA);
            $this->setStore(TAFFEL, $result_itu->getBlock(DZI_DATA));
            /* If rows were found set the browse position to first row */
            if ($result > 0)
                $this->__BrowsePos = 0;
            else
                $this->__BrowsePos = -1;
            $result = NULL;
        } else {
            soDebug("dzLaituri: Query failed.", SOD_LO_E);
            $result = new dzErrorMsg(DZER_QUERY_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR);
            $this->__BrowseKeyCount = -1;
            $this->__BrowsePos = -1;
        } // if $result

        return $result;
    } // runQuery


    /** Release the current lock
     * @return dzErrorMsg   Error message or NULL if all ok.
     */
    function releaseLock() {
        /* Construct the request to send to Kone. First create Itu and fill
           the basic control information in */
        $request = $this->createItu();

        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_UNLOCK);

        /* Insert the key of the requested data as payload */
        $payload = new soSet();
        $key_data = $this->getCurrentBrowseKey();
        $request->setBlock(DZI_DATA, $key_data);

        /* Finally send the request */
        $result_itu = sendRpcToKone($request);

        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text", getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                    DZERT_SYSTEM, DZERL_ERROR,
                                    DZREC_NO_ACTION, $error_param);

            return $result;     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code */
        $result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);

        /* if result ok get the payload and store it */
        if ($result == DZR_OK) {
                $lock_result = "ok";
            sodebug("dzLaituri: release lock request ok", SOD_DET);
            $result = NULL;
        } else {
            sodebug("dzLaituri: release lock request failed:" .
                    $result, SOD_HI);
            $result = new dzErrorMsg(DZER_LOCK_RELEASE_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR);
        } // if result

        return $result;
    } // releaseLock


    /** Read in the list of rows that matches the query keys.
     *  Send a request to Kone.
     *  @param array/string $Cols Names of the list columns
     *  @param int $MaxList Max list length
     *  @return dzErrorMsg   Error message or NULL if all ok.
     */
    function readList($Cols, $MaxList = 0) {
        sodebug("dzLaituri->readList: sending request", SOD_DET);
        /* Construct the request to send to Kone. First create Itu oand fill
           the basic control information in */
        $request = $this->createItu();

        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_LIST);
        /* Set the required column list. */
        $request->setBlockItem(DZI_CONTROL, DZK_DETAILS, $Cols);

        /* Insert the key of the requested data as payload */
        $query_data = $this->getStore(LAST_QUERY);
        $request->setBlock(DZI_DATA, $query_data);

        /* Finally send the request */
        $result_itu = sendRpcToKone($request);
        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri->readList: Rpc failed", SOD_DET);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text", getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                    DZERT_SYSTEM, DZERL_ERROR,
                                    DZREC_NO_ACTION, $error_param);

            return $result;     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code */
        $kone_result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);

        /* if result ok get the payload and store it */
        if ($kone_result > 0) {
            sodebug("dzLaituri->readList: list request ok", SOD_DET);
            $this->setStore(TAFFEL, $result_itu->getBlock(DZI_DATA));

            /* copy the data to TISKI as well */
            $this->copy(TAFFEL, TISKI);
            $result = NULL;
        } else {
            sodebug("dzLaituri->readList: list request failed", SOD_DET);
            $result = new dzErrorMsg(DZER_READ_ERROR, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR);
        } // if result

        return $result;
    } // readList


    /** Fetch data from Kone
     *  Send a fetch request to kone. Use this to get defined fields of
     *  information from a one table.
     *  @param string $Table Table name where the information is fetched
     *  @param array/string $WhereCols Column names to limit return values by.
     *  @param array/any $WhereVals Column values to limit return values by.
     *      Names of the columns to get, should include types data as well
     *      (name:data_type)
     *  @param array/string $ReturnCols Columns to return
     *  @param string $JoinStr URL encoded string that glues together return
     *      column values.
     *  @param soArraySet $ResultSet Result columns
     *  @return NULL|dzErrorMsg NULL when all fine or error object
     */
    function fetch($Table, $WhereCols, $WhereVals, $ReturnCols = array(),
                   $JoinStr = "", &$ResultSet) {
        $DataCols = array_merge($WhereCols, $ReturnCols);
        $WhereVals = soUrlEncodeArray($WhereVals);

        /* Construct the request to send to Kone. First create Itu oand fill
           the basic control information in */
        $request = $this->createItu();

        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_FETCH);

        $fetch_def = "[$Table] table=$Table";
        $fetch_def .= " keycols=".implode("+", $WhereCols);
        $fetch_def .= " keyvals=".$WhereVals;
        $fetch_def .= " datacols=".implode("+", $DataCols);

        sodebug("dzLaituri->fetch: '$fetch_def'", SOD_DET);

        /* Set the required column list. */
        $request->setBlockItem(DZI_CONTROL, DZK_DETAILS, $fetch_def);

        /* Send the request to kone */
        $result_itu = sendRpcToKone($request);

        /* check for rpc error */
        if (isRpcError($result_itu)) {
            sodebug("dzLaituri->fetch(): Rpc failed", SOD_HI);
            /* Get the error message from rpc library and include it
               to the shown error message. */
            $error_param = new soSet();
            $error_param->setItem("error_text", getRpcErrorText($result_itu));
            $result = new dzErrorMsg(DZER_KONE_RPC_FAILED, "Laituri",
                                    DZERT_SYSTEM, DZERL_ERROR,
                                    DZREC_NO_ACTION, $error_param);

            return $result;     // <-- EXIT !!!
        } //if (isRpcError($result_itu))

        /* Get the result code */
        $kone_result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT, 0);

        /* If result ok get the payload and store it */
        if ($kone_result > 0) {
            sodebug("dzLaituri: fetch request ok", SOD_DET);
            $ResultSet = $result_itu->getBlock(DZI_DATA);
            $result = NULL;
        } else {
            sodebug("dzLaituri: fetch request failed", SOD_DET);
            $result = new dzErrorMsg(DZER_FETCH_FAILED, "Laituri",
                                     DZERT_SYSTEM, DZERL_ERROR);
        }; // if result

        return $result;
    } // fetch


    /** Verify the users password
     *  @param string $UserId    The users's ID
     *  @param string $Password The user's password
     *  @return true if user check ok. Otherwise false.
     */
    function verifyUser($UserId, $Password) {
        soDebug("dzLaituri->verifyUser('".$UserId."')", SOD_LO);
        $request = $this->createItu();

        /* set request type & user information */
        $request->setBlockItem(DZI_CONTROL, DZK_REQUEST, DZK_REQ_CHECK_USER);
        $request->setBlockItem(DZI_CONTROL, DZK_USER, $UserId);
        $request->setBlockItem(DZI_CONTROL, DZK_PASSWORD, md5($Password));

        /* Send result to Itu & handle possible error condition */
        $result_itu = sendRpcToKone($request);
        if (isRpcError($result_itu)) {
            $fault_string = getRpcErrorText($result_itu);
            $msg = dzLoginMsg("Rpc failed:".$fault_string, $Lang);
            $state = DZ_LOGIN_STATE_START;
            continue;
        } // if (isRpcError($result_itu))

        $result = $result_itu->getBlockItemAt(DZI_CONTROL, DZK_RESULT);
        switch ($result) {
            case DZVU_UNKNOWN_USER:
            case DZVU_CHECK_FAILED:
                /* Error loging in. Start from beginning */
                $result = false;
                soDebug("dzLaituri: User check failed ", SOD_LO);
            break;

            default:
                /* User ok */
                soDebug("dzLaituri: User ok ", SOD_LO);
                $result = true;
        } // switch ($result)

        return $result;
    } // verifyUser


    /** Processes pre-save options.
     *  Pre-save options most commonly include calls to custom procedures.
     *  @param storeid $StoreID Store to process before saving
     *  @param string $Lang Language code.
     *  @param string $Presave Pre-save eval declaration
     *  @return mixed dzErrorMsg||NULL On error, returns an array of
     *  dzErrorMsg objects, otherwise NULL (no errors).
     */
    function __preSave($StoreID, $Lang, &$Presave) {
        if (!$this->isStore($StoreID)) return NULL; // <-- EXIT

        $store = $this->getStore($StoreID);
        $errors = array();
        $eval = soSplitStr($Presave, ":", 1);
        if (count($eval)) {
            $eval[0] = str_replace("~", dzGetMainScriptPath(), $eval[0]);
            eval("include_once(\"".$eval[0]."\"); ".
                "\$result_set = ".$eval[1]."(\$store);");
            if ($result_set) {
                if (is_array($result_set)) {
                    $this->__preSave_sub($StoreID, $result_set);
                };
            } else {
                $params = new soSet(array("presave" => $Presave));
                $errors[] = new dzErrorMsg(DZER_PRESAVE_FAILED, "",
                                           DZERT_USER, DZERL_ERROR,
                                           DZREC_NO_ACTION, $params);
            };
        }; // if

        if (!$errors) {
            return $errors;
        } else {
            return NULL;
        }; // else if
    } // __preSave


    /** Processes any result set returned by eval procedure.
     *  @param storeid $StoreID Store to be processed before saving
     *  @param object soArraySet $ResultSet A result set.
     */
    function __preSave_sub($StoreID, &$ResultSet) {
        foreach ($ResultSet as $key => $value) {
            $col = soExtractVal($key, "#", 1);
            $rowno = intval(soExtractVal($key, "#", 2));
            $this->setStoreItem($StoreID, $col, $rowno, $value);
        }; // foreach key
    } // __preSave_sub

    /** Validates a set/store in Purkki.
     *  When a set being validated contains invalid values, information
     *  about the issue is stored in __OutItu.
     *  @param storeid $StoreID Store to be validated.
     *  @param string $Lang Language code.
     *  @return mixed dzErrorMsg||NULL On error, returns an array of
     *  dzErrorMsg objects, otherwise NULL (no errors).
     */
    function __validateSet($StoreID, $Lang) {

        if (!$this->isStore($StoreID)) return NULL; // <-- EXIT

        $store = $this->getStore($StoreID);
        $keys = $store->getKeys();
        $errors = array();

        foreach ($keys as $key) {
            if ($result =
                $this->__validateSet_unique($store, $key, $Lang)) {
                $errors[] = $result;
            }; // if

            if ($result =
                $this->__validateSet_range($store, $key, $Lang)) {
                $errors[] = $result;
            }; // if

            if ($result =
                $this->__validateSet_required($store, $key, $Lang)) {
                $errors[] = $result;
            }; // if
        }; // foreach

        if ($errors) {
            return $errors;
        } else {
            return NULL;
        };
    } // __validateSet


    /** Unique value check for a set.
     *  This check is only useful for multirow columns. It checks whether
     *  all column values are unique, i.e. each row has a unique value in
     *  that column.
     *  @param dzItu $Store Store to check.
     *  @param string $Key Column to check
     *  @return object dzErrorMsg||NULL Returns error msg object when double
     *  values are found, otherwise NULL.
     */
    function __validateSet_unique(&$Store, $Key, $Lang) {
        $result = NULL;
        if ($this->__Struct->getColProperty($Key, DZCP_UNIQUE)) {
            $values = $Store->getItem($Key);
            $doubles = soGetArrayDoubles($values);
            if (count($doubles)) {
                $row_nos = "";
                foreach ($doubles as $double) {
                    $row_nos = soJoinStr($row_nos, ", ", ($double +1), 1);
                }; // foreach
                list($table_name, $col_name) = soExplode($Key, ".");
                $dbalias = dzGetIniItem(DZIS_GENERAL, DZID_DBALIAS, "app");
                $key_name = "'".dzGetDBTranslation($dbalias, $table_name,
                                                   $col_name, $Lang)."'";
                $params = new soSet(array("column" => $key_name,
                                          "rows" => $row_nos));
                $result = new dzErrorMsg(DZER_UNIQUE_CHECK_FAILED, "",
                                         DZERT_USER, DZERL_ERROR,
                                         DZREC_NO_ACTION, $params);
            }; // if soGetArrayDoubles
        }; // if DZCP_UNIQUE

        return $result;
    } // __validateSet_unique


    /** Unique value check for a set.
     *  This check is only useful for multirow columns. It checks whether
     *  all column values are unique, i.e. each row has a unique value in
     *  that column.
     *  Note, that column range may include more than one range, a set of
     *  ranges. See dzStruct for more details - and dataxi-forms-howto.
     *
     *  @param dzItu $Store Store to check.
     *  @param string $Key Column to check
     *  @return object dzErrorMsg||NULL Returns error msg object when double
     *  values are found, otherwise NULL.
     */
    function __validateSet_range(&$Store, $Key, $Lang) {
        $result = NULL;
        $ranges = $this->__Struct->getColProperty($Key, DZCP_RANGE);

        /** Apparently pseudo cols might have an empty string as their range
         *  array. This is why to ensure null/"" ranges are converted to
         *  array() */
        if (!$ranges) $ranges = array();

        if (!count($ranges)) return NULL; // <-- EXIT - nothing to range check

        $values = $Store->getItem($Key);
        $col_type = $this->__Struct->getDatatype($Key);
        foreach ($values as $rowno => $value) {
            /* Other than int and dec requires modifying the actual
             * value. For example, strings max/min are determined by string
             * lenght, not by string value. */
            switch ($col_type) {
                case SODT_TEXT:
                    $value = strlen($value);
                    break;
                case SODT_DATE:
                    $value = date("Ymd", intval($value *SECS_PER_DAY));
                    break;
            }; // switch

            /* Test against all ranges, find a match. */
            $match = 0;
            $r = count($ranges);
            while ((--$r >= 0) && (!$match)) {
                $min = $ranges[$r]["min"];
                $max = $ranges[$r]["max"];
                if (((!$min) || ($value >= $min)) &&
                    ((!$max) || ($value <= $max))) {
                    $match = 1;
                }; // if

            }; // foreach $ranges
            /* Did all range checks fail? If so, set error msg */
            if (!$match) {
                switch ($col_type) {
                    case SODT_TEXT:
                        $err_msg = dzGetSysMsg(basename(__FILE__),
                                            "str_range_check",
                                            $Lang);
                        break;

                    case SODT_INT:
                    case SODT_DEC:
                        $err_msg = dzGetSysMsg(basename(__FILE__),
                                            "range_check",
                                            $Lang);
                        break;
                }; // switch
                $or_msg = dzGetSysMsg(basename(__FILE__),
                                   "or",
                                   $Lang);
                $range_str = "";
                foreach ($ranges as $range) {
                    $range_str = soJoinStr($range_str,
                                           " ".$or_msg." ",
                                           intval($range["min"])." - ".
                                           intval($range["max"]),
                                           1);
                }; // foreach
                list($table_name, $col_name) = soExplode($Key, ".");
                $dbalias = dzGetIniItem(DZIS_GENERAL, DZID_DBALIAS, "app");
                $key_name = "'".dzGetDBTranslation($dbalias, $table_name,
                                                   $col_name, $Lang)."'";
                $params = new soSet(array("column" => $key_name,
                                          "row" => ($rowno +1),
                                          "reason" => $err_msg." ".$range_str,
                                          "now" => $value));
                if (count($values) > 1) {
                    $error_no = DZER_SET_VALIDATION_FAILED_MULTIROW;
                } else {
                    $error_no = DZER_SET_VALIDATION_FAILED;
                };
                $result = new dzErrorMsg($error_no,
                                        "", DZERT_USER, DZERL_ERROR,
                                        DZREC_NO_ACTION, $params);
            }; // if !$match
        }; // foreach $values

        return $result;
    } // __validateSet_range


    /** Required value check for a set.
     *  Checks whether required  field is really filled in.
     *  @param dzItu $Store Store to check.
     *  @param string $Key Column to check
     *  @return object dzErrorMsg||NULL Returns error msg object when double
     *  values are found, otherwise NULL.
     */
    function __validateSet_required(&$Store, $Key, $Lang) {
        $result = NULL;
        $default = $this->__Struct->getColProperty($Key, DZCP_DEFAULT);
        if ($this->__Struct->getColProperty($Key, DZCP_REQUIRED) &&
            ereg(DZDCV_AUTO, $default)) {
            $values = $Store->getItem($Key);
            $icount = count($values);
            for ($i = 0; $i < $icount; $i++) {
                if (soIsNull($values[$i]) || soIsEmptyStr($values[$i])) {
                    list($table_name, $col_name) = soExplode($Key, ".");
                    $dbalias = dzGetIniItem(DZIS_GENERAL, DZID_DBALIAS, "app");
                    $key_name = "'".dzGetDBTranslation($dbalias, $table_name,
                                                       $col_name, $Lang)."'";
                    $params = new soSet(array("column" => $key_name,
                                              "row" => ($i+1)));
                    $result = new dzErrorMsg(DZER_REQUIRED_CHECK_FAILED, "",
                                             DZERT_USER, DZERL_ERROR,
                                             DZREC_NO_ACTION, $params);
                }; // if empty/null
            }; // for
        }; // if DZCP_REQUIRED

        return $result;
    } // __validateSet_unique


}; // dzLaituri

?>
